package com.codeczx.myretrofitdemo.retrofit;

import android.util.Log;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;

import okhttp3.*;
import okhttp3.Call;

import static java.util.Collections.unmodifiableList;

/**
 * Created by codeczx on 2018/3/7.
 */

public final class Retrofit {

    private static final String TAG = "Retrofit";
    HttpUrl baseUrl;
    private CallAdapter callAdapter;
    private List<CallAdapter.Factory> adapterFactories;
    private List<Converter.Factory> converterFactories;
    Executor callbackExecutor;
    private Call.Factory callFactory;


    Retrofit(HttpUrl baseUrl, List<Converter.Factory> converterFactories,
             List<CallAdapter.Factory> adapterFactories,okhttp3.Call.Factory callFactory,
             Executor callbackExecutor) {
        this.baseUrl = baseUrl;
        this.adapterFactories = unmodifiableList(adapterFactories);
        this.converterFactories = unmodifiableList(converterFactories);
        this.callFactory = callFactory;
        this.callbackExecutor = callbackExecutor;
    }

    @SuppressWarnings("unchecked")
    public <T> T create(final Class<T> service) {
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Log.i(TAG, "invoke: " + method.toString());
                Log.i(TAG, "invoke: " + method.getAnnotations().length);
                for (Annotation annotation : method.getAnnotations()) {
                    Log.i(TAG, "invoke: " + annotation);
                }
                ServiceMethod serviceMethod = loadServiceMethod(method);
                OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod,args);
                return serviceMethod.callAdapter.adapt(okHttpCall);
            }
        });
    }

    public CallAdapter<?, ?> callAdapter(Annotation[] annotations, Type returnType) {
        return nextCallAdapter(annotations, returnType);
    }

    private CallAdapter<?, ?> nextCallAdapter(Annotation[] annotations, Type returnType) {
        Log.i(TAG, "nextCallAdapter: " + adapterFactories.size());
        for (int i = 0; i < adapterFactories.size(); i++) {
            CallAdapter<?, ?> adapter = adapterFactories.get(i).get(returnType, annotations);
            if (adapter != null) {
                return adapter;
            }
        }
        return null;
    }

    public Converter<?,String> stringConverter(Type type,Annotation[] annotations){
        for (int i = 0; i < converterFactories.size(); i++) {
            Converter<?,String> converter = converterFactories.get(i).stringConverter(type,annotations,this);
            if(converter !=null){
                return converter;
            }
        }
        return BuiltInConverters.ToStringConverter.INSTANCE;
    }

    private ServiceMethod<?,?> loadServiceMethod(Method method) {
        return new ServiceMethod.Builder<>(this, method).build();
    }

    public Call.Factory callFactory() {
        return this.callFactory;
    }

    public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
        return nextResponseBodyConverter(null,type,annotations);
    }

    private <T> Converter<ResponseBody, T> nextResponseBodyConverter(Object o, Type type, Annotation[] annotations) {
        Converter<ResponseBody,?> converter = null;
        Log.i(TAG, "nextResponseBodyConverter: "+converterFactories.size());
        for (int i = 0; i < converterFactories.size(); i++) {
            converter = converterFactories.get(i).responseBodyConverter(type,annotations,this);
        }
        Log.i(TAG, "nextResponseBodyConverter: "+converter);
        return (Converter<ResponseBody, T>) converter;
    }


    public static final class Builder {

        private final Platform platform;
        private HttpUrl baseUrl;
        private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
        private List<Converter.Factory> converterFactories = new ArrayList<>();
        Executor callbackExecutor;
        private Call.Factory callFactory;

        public Builder() {
            platform = Platform.get();
            converterFactories.add(new BuiltInConverters());
        }

        public Builder baseUrl(String baseUrl) {
            HttpUrl httpUrl = HttpUrl.parse(baseUrl);
            this.baseUrl = httpUrl;
            return this;
        }

        public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
            adapterFactories.add(factory);
            return this;
        }

        public Builder addConverterFactory(Converter.Factory factory){
            converterFactories.add(factory);
            return this;
        }

        /**
         * 整理通过构造器模式获得的对象，构造真正的retrofit对象
         * adapterFactories和converterFactories的默认元素的添加位置为什么不同？
         * @return
         */
        public Retrofit build() {

            okhttp3.Call.Factory callFactory = this.callFactory;
            if(callFactory==null){
                callFactory = new OkHttpClient().newBuilder()
                .connectTimeout(5000, TimeUnit.MILLISECONDS)
                .readTimeout(5000,TimeUnit.MILLISECONDS)
                .build();
            }
            Executor callbackExecutor = this.callbackExecutor;
            if (callbackExecutor == null) {
                callbackExecutor = platform.defaultCallbackExecutor();
            }

            List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
            adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

            List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
            return new Retrofit(baseUrl, converterFactories,adapterFactories,callFactory,callbackExecutor);
        }

    }
}
